/**
 * $Id$
 */

import java.util.*;

/**
 * Represents the visitors for Interpreter that visit the nodes in the parse
 * tree. This also stores the assignation table for assigned values, and a
 * function table for representing defined functions and their scopes. Finally,
 * error reporting is also handled here, where errors occur that were not picked
 * up by the parser (example: divide by zero.)
 * 
 * @author Vic Fryzel <vef3240@rit.edu>
 */
public class InterpreterElementDoVisitor implements Parser.yyTree.Visitor {

	protected Stack<Map<Object, Object>> hashTables;

	/**
	 * The constructor for this object. This does nothing but setup the
	 * assignationTable and functionTable members.
	 */
	public InterpreterElementDoVisitor() {
		hashTables = new Stack<Map<Object, Object>>();
		hashTables.push(new HashMap<Object, Object>());
	}

	@Override
	public Object visit(Parser.yyTree.Program node) {
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
		}
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Statement node) {
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
		}
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Assign node) {
		Object retval = null;
		if (node.size() == 2) {
			this.hashTables.peek().put(
					(String) ((Parser.yyTree.Visit) node.get(0)).visit(this),
					((Parser.yyTree.Visit) node.get(1)).visit(this));
			retval = this.hashTables.peek().get(node.get(0));
		} else {
			// TODO: Add error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Select node) {
		Object retval = null;
		if (node.size() >= 1) {
			Object conditional = ((Parser.yyTree.Visit) node.get(0))
					.visit(this);
			if (conditional != null && !Boolean.FALSE.equals(conditional)) {
				for (int i = 1; i < node.size(); i++) {
					((Parser.yyTree.Visit) node.get(i)).visit(this);
				}
				retval = node;
			} else if (node.size() >= 2
					&& node.get(node.size() - 1).getClass().getName().equals(
							"SelectElse")) {
				retval = ((Parser.yyTree.Visit) node.get(node.size() - 1))
						.visit(this);
			}
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.SelectElse node) {
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
		}
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Loop node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object conditional = ((Parser.yyTree.Visit) node.get(0))
					.visit(this);
			while (conditional != null && !Boolean.FALSE.equals(conditional)) {
				retval = node;
				for (int i = 1; i < node.size(); i++) {
					((Parser.yyTree.Visit) node.get(i)).visit(this);
				}
				conditional = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			}
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Begin node) {
		this.hashTables.push(new HashMap<Object, Object>());
		for (int i = 0; i < node.size(); i++) {
			((Parser.yyTree.Visit) node.get(i)).visit(this);
		}
		this.hashTables.pop();
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Print node) {
		for (int i = 0; i < node.size(); i++) {
			System.out.print(((Parser.yyTree.Visit) node.get(i)).visit(this)
					.toString());
		}
		return node;
	}

	@Override
	public Object visit(Parser.yyTree.Expression node) {
		Object retval = null;
		if (node.size() == 1) {
			if (node.get(0).getClass().getName().equals("java.lang.Integer")
					|| node.get(0).getClass().getName().equals(
							"java.lang.String")) {
				retval = node.get(0);
			} else {
				retval = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Add node) {
		Integer retval = null;
		if (node.size() >= 2) {
			retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				retval += (Integer) value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Dif node) {
		Integer retval = null;
		if (node.size() >= 2) {
			retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				retval -= (Integer) value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Mul node) {
		Integer retval = null;
		if (node.size() >= 2) {
			retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				retval *= (Integer) value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Div node) {
		Integer retval = null;
		if (node.size() >= 2) {
			retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				// Disallow divide by zero
				if ((Integer) value != 0) {
					retval /= (Integer) value;
				}
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Mod node) {
		Integer retval = null;
		if (node.size() >= 2) {
			retval = (Integer) ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				retval %= (Integer) value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Eq node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if (!prevValue.equals(value)) {
					retval = Boolean.FALSE;
					break;
				} else {
					retval = Boolean.TRUE;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Neq node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if (!prevValue.equals(value)) {
					retval = Boolean.TRUE;
					break;
				} else {
					retval = Boolean.FALSE;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Gt node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if ((Integer) prevValue > (Integer) value) {
					retval = Boolean.TRUE;
				} else {
					retval = Boolean.FALSE;
					break;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Gte node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if ((Integer) prevValue >= (Integer) value) {
					retval = Boolean.TRUE;
				} else {
					retval = Boolean.FALSE;
					break;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Lt node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if ((Integer) prevValue < (Integer) value) {
					retval = Boolean.TRUE;
				} else {
					retval = Boolean.FALSE;
					break;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Lte node) {
		Object retval = null;
		if (node.size() >= 2) {
			Object prevValue = ((Parser.yyTree.Visit) node.get(0)).visit(this);
			for (int i = 1; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if ((Integer) prevValue <= (Integer) value) {
					retval = Boolean.TRUE;
				} else {
					retval = Boolean.FALSE;
					break;
				}
				prevValue = value;
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.And node) {
		Object retval = null;
		if (node.size() >= 2) {
			for (int i = 0; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if (value == null || value == Boolean.FALSE
						|| value == Integer.valueOf(0)) {
					retval = Boolean.FALSE;
					break;
				}
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Or node) {
		Object retval = null;
		if (node.size() >= 2) {
			for (int i = 0; i < node.size(); i++) {
				Object value = ((Parser.yyTree.Visit) node.get(i)).visit(this);
				if (value != null && value != Boolean.FALSE
						&& value != Integer.valueOf(0)) {
					retval = Boolean.TRUE;
					break;
				}
			}
		} else {
			// TODO: Error
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Str node) {
		Object retval = null;
		if (node.size() == 1) {
			String val = ((String) node.get(0));
			val = val.substring(1);
			val = val.substring(0, val.length() - 1);
			retval = val;
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.VarName node) {
		Object retval = null;
		if (node.size() == 1) {
			retval = ((String) node.get(0));
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Var node) {
		Object retval = new Integer(0);
		if (node.size() == 1) {
			String name = (String) node.get(0);
			// Iterate up the hashTables, find a match if there is one
			Stack<Map<Object, Object>> copy = (Stack<Map<Object, Object>>) this.hashTables
					.clone();
			Map<Object, Object> current = copy.pop();
			try {
				while (!current.containsKey(name)) {
					current = copy.pop();
				}
				retval = current.get(name);
			} catch (EmptyStackException ese) {
				// We didn't find a match
				// Do nothing, we already defaulted to 0
			}
		}
		return retval;
	}

	@Override
	public Object visit(Parser.yyTree.Visit node) {
		// TODO Auto-generated method stub
		return null;
	}
}
